#include "lobbystate.h"
#include "leveltransitionstate.h"

void LobbyState::ServerInit()
{
	// Startup as server.
	m_server = new RakNetServer;
	m_work_manager = m_server->work_manager();
	m_server->Start(4437);
	m_guid = m_server->GetGUID();

	// Buttons for selecting what number of players the levels should have.
	Button* increase_player_number_set_button = new Button("increase_player_number_set_button", 760, 10, 20, 20,
		[this]()
	{
		if (++m_number_of_players_set > MAX_PLAYERS)
			m_number_of_players_set = 1;
			
		ServerLoadLevelList();
	});
	Button* decrease_player_number_set_button = new Button("decrease_player_number_set_button", 700, 10, 20, 20,
		[this]()
	{
		if (--m_number_of_players_set < 1)
			m_number_of_players_set = MAX_PLAYERS;
			
		ServerLoadLevelList();
	});
	m_dialog->AddElement(increase_player_number_set_button);
	m_dialog->AddElement(decrease_player_number_set_button);

	// Load the level list for the first time.
	ServerLoadLevelList();

	// This timer is for lettings us know when the time has passed to send the level info to the clients again.
	// We don't want to send this too often in order to save bandwidth.
	m_send_level_info_timer = al_create_timer(1.0f);
	al_start_timer(m_send_level_info_timer);

	// Construct the chatfield for the server.
	ALLEGRO_FONT* chatfield_font = al_load_font(STANDARD_FONT.c_str(), STANDARD_FONT_SIZE, NULL);
	TextField* chatfield = new TextField("chatfield", 10, 728, 1004, 30,
		std::bind(&LobbyState::ServerEnterChatMessage, this, std::placeholders::_1), chatfield_font,
		STANDARD_TEXTFIELD_TEXT_COLOR, STANDARD_TEXTFIELD_PADDING, 1024);
	m_dialog->AddElement(chatfield);

	Button* start_game_button = new Button("start_game_button", 714, 440, 300, 50,
		std::bind(&LobbyState::ServerStartGame, this));
	m_dialog->AddElement(start_game_button);
}

void LobbyState::ServerCleanup()
{
	al_destroy_timer(m_send_level_info_timer);

	if (m_shutdown_network_on_state_end)
	{
		m_server->Shutdown();
		delete m_server;
	}
}

void LobbyState::DoServerWork()
{
	// Get Jobs done.
	Job next_job;
	while (m_work_manager->GetJobToLocal(next_job))
	{
		unsigned char type_id;
		next_job.data->Read(type_id);
		switch (type_id)
		{
		case ID_NEW_INCOMING_CONNECTION:
			ServerProcessIncomingConnection(next_job);
			break;

		case ID_DISCONNECTION_NOTIFICATION:
		case ID_CONNECTION_LOST:
			ServerRemovePlayer(next_job.sender_guid);
			break;

		case ID_CLIENT_INFORMATION:
			ServerProcessClientInformation(next_job);
			break;

		case ID_CHAT_MESSAGE:
			ServerProcessChatMessage(next_job);
			break;
		};

		next_job.JobDone();
	}
}

void LobbyState::ServerProcessIncomingConnection(Job incoming_connection_job)
{
	// Add button to remove the client.
	m_dialog->AddElement(new Button("button_kick_" + incoming_connection_job.sender_guid, 10,
		10 + m_client_list.GetSize() * 30, 35, 30, 
		std::bind(&LobbyState::ServerKickPlayer, this, incoming_connection_job.sender_guid)));

	// Add the client to the list.
	ClientConnection new_client;
	new_client.set_guid(incoming_connection_job.sender_guid);
	m_client_list.AddClient(new_client);

	// Request client for his information.
	Job job(ID_CLIENT_INFORMATION_REQUEST);
	job.receive_guid = incoming_connection_job.sender_guid;
	m_work_manager->JobToRemote(job);

	// Tell the client of everybody else.
	for (unsigned i = 0; i < m_client_list.GetSize(); i++)
	{
		ClientConnection client;
		m_client_list.GetClient(i, client);
						
		Job job(ID_CLIENT_INFORMATION);
		RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(client.guid().c_str()), 64, job.data);
		RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(client.name().c_str()), 64, job.data);
		for (int p = 0; p < MAX_PLAYERS; p++)
			job.data->Write(client.control_at(p));

		job.receive_guid = incoming_connection_job.sender_guid;
		job.broadcast = false;
		m_work_manager->JobToRemote(job);
	}
}

void LobbyState::ServerRemovePlayer(std::string guid)
{
	// The player might have had helpsymbols active, so a redraw might be necessary to get rid of them.
	m_rebuild_color_helpsymbols = true;

	// Remove the client.
	unsigned client_number;
	if (m_client_list.RemoveClient(guid, client_number))
	{
		// Remove the kick button.
		m_dialog->RemoveElementOnNextUpdate("button_kick_" + guid);

		// Move up the kick buttons that were below the kicked client.
		for (unsigned i = client_number; i < m_client_list.GetSize(); i++)
		{
			ClientConnection client;
			m_client_list.GetClient(i, client);

			GuiElement* element;
			if (m_dialog->GetElement("button_kick_" + client.guid(), element))
				element->MovePosition(0, -30);
		}
	}

	// Tell everyone that the client removed is not there anymore.				
	Job job(ID_REMOVE_CLIENT);
	RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(guid.c_str()), 64, job.data);

	job.broadcast = true;
	m_work_manager->JobToRemote(job);
}

void LobbyState::ServerKickPlayer(std::string guid)
{
	// Close the connection. This will automatically trigger an ID_DISCONNECTION_NOTIFICATION which will then
	// trigger the ServerRemovePlayer command below.
	m_server->CloseConnection(guid);
}

void LobbyState::ServerProcessClientInformation(Job connection_information_job)
{
	RakNet::RakString guid_string;
	RakNet::StringCompressor::Instance()->DecodeString(&guid_string, 64, connection_information_job.data);
	std::string guid(guid_string.C_String());

	// Fill in client information.
	ClientConnection* client_to_update;
	if (m_client_list.GetClient(guid, client_to_update))
	{
		// Set all attributes.
		RakNet::RakString name_string;
		RakNet::StringCompressor::Instance()->DecodeString(&name_string, 64, connection_information_job.data);
		client_to_update->set_name(std::string(name_string.C_String()));
		for (int p = 0; p < MAX_PLAYERS; p++)
		{
			bool control;
			connection_information_job.data->Read(control);
			client_to_update->set_control_at(p, control);
		}

		// The player may now control different colors, so update the help symbols.
		m_rebuild_color_helpsymbols = true;

		// Tell everyone else who the new client is.
		Job job(ID_CLIENT_INFORMATION);
		RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(guid.c_str()), 64, job.data);
		RakNet::StringCompressor::Instance()->EncodeString(name_string, 64, job.data);
		for (int p = 0; p < MAX_PLAYERS; p++)
			job.data->Write(client_to_update->control_at(p));

		job.broadcast = true;
		m_work_manager->JobToRemote(job);
	}
}

void LobbyState::ServerProcessChatMessage(Job chat_message_job)
{
	RakNet::RakString message_string;
	RakNet::StringCompressor::Instance()->DecodeString(&message_string, 1024, chat_message_job.data);

	unsigned char color[3];
	for (unsigned c = 0; c < 3; c++)
		chat_message_job.data->Read(color[c]);

	// Pass the message to every client.
	Job job(ID_CHAT_MESSAGE);
	RakNet::StringCompressor::Instance()->EncodeString(message_string, 1024, job.data);
	job.data->Write(color[0]);
	job.data->Write(color[1]);
	job.data->Write(color[2]);

	job.broadcast = true;
	m_work_manager->JobToRemote(job);

	// Show in the chatbox.
	GuiElement* element;
	if (m_dialog->GetElement("chatbox", element))
	{
		((TextBox*)element)->AddLine(std::string(message_string.C_String()), color);
	}
}

void LobbyState::ServerEnterChatMessage(std::string message)
{
	GuiElement* element;
	if (m_dialog->GetElement("chatfield", element))
	{
		((TextField*)element)->DeleteText();
	}

	message = "[" + m_client_name + "]: " + message;
	if (m_dialog->GetElement("chatbox", element))
	{
		((TextBox*)element)->AddLine(message);
	}

	ServerSendChatMessage(message, STANDARD_TEXTBOX_TEXT_COLOR);
}

void LobbyState::ServerSendChatMessage(std::string message, const unsigned char color[3])
{
	// Send the message to everyone else.
	Job job(ID_CHAT_MESSAGE);
	RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(message.c_str()), 1024, job.data);
	job.data->Write(color[0]);
	job.data->Write(color[1]);
	job.data->Write(color[2]);

	job.broadcast = true;
	m_work_manager->JobToRemote(job);
}

void LobbyState::ServerLoadLevelList()
{
	// This might influence the control symbols, so do a redraw.
	m_rebuild_color_helpsymbols = true;

	// Clear the current list.
	m_levels.clear();
	m_dialog->RemoveElement("levellist");

	ALLEGRO_FONT* levellist_label_font = al_load_font(STANDARD_FONT.c_str(), STANDARD_FONT_SIZE, NULL);
	m_dialog->AddElement(new ValueList("levellist", 500, 50, 500, 206, levellist_label_font));

	// Parse the xml file.
	rapidxml::file<> levelinfo_xml_file(STANDARD_LEVELINFO_FILE.c_str());
	rapidxml::xml_document<> doc;
	doc.parse<0>(levelinfo_xml_file.data());

	std::string player_number_node_name = std::to_string(m_number_of_players_set) + "Players";
	rapidxml::xml_node<>* node = doc.first_node()->first_node(player_number_node_name.c_str());

	// Read all levels that exist for the game mode (if there are levels for that many players).
	if (node)
	{
		for(rapidxml::xml_node<> *level_node = node->first_node("Level"); level_node; level_node = level_node->next_sibling())
		{
			// Store the level info in the map.
			LevelInfo level;
			level.id = boost::lexical_cast<std::int32_t>(level_node->first_node("ID")->value());
			level.number = boost::lexical_cast<std::int32_t>(level_node->first_node("Number")->value());
			level.player_number = boost::lexical_cast<std::int32_t>(level_node->first_node("PlayerNumber")->value());
			level.name = level_node->first_node("Name")->value();
			level.filename = level_node->first_node("Filename")->value();

			m_levels.insert(std::pair<std::string, LevelInfo>(level.name, level));

			// Add the level to the list so that it can be selected.
			GuiElement* levellist;
			m_dialog->GetElement("levellist", levellist);
			((ValueList*)levellist)->AddEntry(level.name, level.name);
		}
	}
}

void LobbyState::ServerStartGame()
{
	// TODO: Only if everything is prepared for game start.

	m_game_started = true;
	m_shutdown_network_on_state_end = false;

	Job job(ID_START_GAME);
	job.broadcast = true;
	m_work_manager->JobToRemote(job);

	LevelTransitionState* next_state = new LevelTransitionState(m_engine);
	next_state->Init(m_is_server, m_server, m_client, m_client_list, m_current_level.filename);
	m_engine->SetNextState(next_state);
}

void LobbyState::ServerUpdate()
{
	DoServerWork();

	// Check whether a level was selected and if so, store the information.
	m_level_chosen = false;
	GuiElement* level_list;
	if (m_dialog->GetElement("levellist", level_list))
	{
		std::string chosen_level;
		m_level_chosen = ((ValueList*)level_list)->GetCurrentValue(chosen_level);

		if (m_level_chosen)
		{
			m_current_level = m_levels.at(chosen_level);
		}
	}

	// Send level info not more than once per second.
	if (al_get_timer_count(m_send_level_info_timer) > 0)
	{
		al_set_timer_count(m_send_level_info_timer, 0);

		Job job(ID_LEVELINFO);
		job.data->Write(m_level_chosen);
		if (m_level_chosen)
		{
			job.data->Write(m_current_level.id);
			job.data->Write(m_current_level.number);
			job.data->Write(m_current_level.player_number);
			RakNet::StringCompressor::Instance()->EncodeString(RakNet::RakString(m_current_level.name.c_str()), 128, job.data);
		}

		job.broadcast = true;
		m_work_manager->JobToRemote(job);

		// We don't want to draw the color helpsymbols when no level is chosen. This means, an update might be necessary.
		m_rebuild_color_helpsymbols = true;
	}
}